﻿using System.Collections;
using System.Runtime.CompilerServices;

namespace IdGen;

/// <summary>
/// 生成受雪花项目启发的标识符
/// </summary>
public class IdGenerator : IIdGenerator<long>
{
    private readonly long _generatorid;
    private int _sequence = 0;
    private long _lastgen = -1;

    private readonly long MASK_SEQUENCE;
    private readonly long MASK_TIME;
    private readonly long MASK_GENERATOR;

    private readonly int SHIFT_TIME;
    private readonly int SHIFT_GENERATOR;

    // Object to lock() on while generating Id's
    private readonly object _genlock = new();

    public IdGeneratorOptions Options { get; }

    public int Id => (int)_generatorid;

    public IdGenerator(int generatorId)
        : this(generatorId, new IdGeneratorOptions()) { }

    public IdGenerator(int generatorId, IdGeneratorOptions options)
    {
        _generatorid = generatorId;
        Options = options ?? throw new ArgumentNullException(nameof(options));

        var maxgeneratorid = (1U << Options.IdStructure.GeneratorIdBits) - 1;

        if (_generatorid < 0 || _generatorid > maxgeneratorid)
        {
            throw new ArgumentOutOfRangeException(nameof(generatorId), string.Format("生成器标识（GeneratorId）必须在 0 到 {0} 之间", maxgeneratorid));
        }

        // 预先计算一些值
        MASK_TIME = GetMask(options.IdStructure.TimestampBits);
        MASK_GENERATOR = GetMask(options.IdStructure.GeneratorIdBits);
        MASK_SEQUENCE = GetMask(options.IdStructure.SequenceBits);
        SHIFT_TIME = options.IdStructure.GeneratorIdBits + options.IdStructure.SequenceBits;
        SHIFT_GENERATOR = options.IdStructure.SequenceBits;
    }

    /// <summary>
    /// 创建一个新的Id
    /// </summary>
    public long CreateId()
    {
        var id = CreateIdImpl(out var ex);
        return ex != null ? throw ex : id;
    }

    /// <summary>
    /// 尝试生成一个新的标识符
    /// </summary>
    public bool TryCreateId(out long id)
    {
        id = CreateIdImpl(out var ex);
        return ex == null;
    }

    /// <summary>
    /// 创建一个新的Id
    /// </summary>
    private long CreateIdImpl(out Exception? exception)
    {
        lock (_genlock)
        {
            // Determine "timeslot" and make sure it's >= last timeslot (if any)
            var ticks = GetTicks();
            var timestamp = ticks & MASK_TIME;

            if (timestamp < _lastgen || ticks < 0)
            {
                exception = new InvalidSystemClockException(string.Format("时钟往回移动了或者产生了循环。在接下来的 {0} 个时间刻度内拒绝生成标识符", _lastgen - timestamp));
                return -1;
            }

            // If we're in the same "timeslot" as previous time we generated an Id, up the sequence number
            if (timestamp == _lastgen)
            {
                if (_sequence >= MASK_SEQUENCE)
                {
                    switch (Options.SequenceOverflowStrategy)
                    {
                        case SequenceOverflowStrategy.SpinWait:
                            SpinWait.SpinUntil(() => _lastgen != GetTicks());
                            return CreateIdImpl(out exception); // Try again
                        case SequenceOverflowStrategy.Throw:
                        default:
                            exception = new SequenceOverflowException("序列溢出。在当前时间刻度剩余的时间内拒绝生成标识符");
                            return -1;
                    }
                }
                _sequence++;
            }
            else // We're in a new(er) "timeslot", so we can reset the sequence and store the new(er) "timeslot"
            {
                _sequence = 0;
                _lastgen = timestamp;
            }

            // If we made it here then no exceptions occurred; make sure we communicate that to the caller by setting `exception` to null
            exception = null;
            // Build id by shifting all bits into their place
            return (timestamp << SHIFT_TIME)
                | (_generatorid << SHIFT_GENERATOR)
                | (long)_sequence;
        }
    }

    public Id FromId(long id) =>
        // Deconstruct Id by unshifting the bits into the proper parts
        new(
            (int)(id & MASK_SEQUENCE),
            (int)((id >> SHIFT_GENERATOR) & MASK_GENERATOR),
            Options.TimeSource.Epoch.Add(TimeSpan.FromTicks(((id >> SHIFT_TIME) & MASK_TIME) * Options.TimeSource.TickDuration.Ticks))
        );

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private long GetTicks() => Options.TimeSource.GetTicks();

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static long GetMask(byte bits) => (1L << bits) - 1;

    /// <summary>
    /// 返回一个 “永不停歇” 的标识符流
    /// </summary>
    /// <returns>“永不停歇” 的标识符流</returns>
    private IEnumerable<long> IdStream()
    {
        while (true)
        {
            yield return CreateId();
        }
    }

    public IEnumerator<long> GetEnumerator() => IdStream().GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}